﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Collections;

namespace Elentok.Common.Wpf.Controls
{
  /// <summary>
  /// Interaction logic for SortableListView.xaml
  /// </summary>
  public partial class SortableListView : ListView
  {
    #region Ctor

    /// <summary>
    /// Initializes a new instance of the <see cref="SortableListView"/> class.
    /// </summary>
    public SortableListView()
    {
      InitializeComponent();
    }

    #endregion

    #region Sort: Private Data Members
    private string currentSortBy = null;
    private bool isSorting = false;
    private GridViewColumn previousSortColumn;
    private ListSortDirection previousSortDirection;
    #endregion

    #region Sort: Private Helper Methods
    private void GridViewColumnHeaderClicked(object sender, RoutedEventArgs e)
    {
      GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;

      if (headerClicked != null && headerClicked.Column != null)
      {
        ListSortDirection direction = ListSortDirection.Ascending;
        
        if (previousSortColumn == headerClicked.Column)
        {
          if (previousSortDirection == ListSortDirection.Ascending)
            direction = ListSortDirection.Descending;
          else
            direction = ListSortDirection.Ascending;
        }

        Sort(headerClicked.Column.Header as string, direction);
      }
    }

    private void Sort(string sortBy)
    {
      #region Parse the direction from the given "sortBy" string
      ListSortDirection direction = ListSortDirection.Ascending;

      if (sortBy.EndsWith(" DESC"))
      {
        direction = ListSortDirection.Descending;
        sortBy = sortBy.Substring(0, sortBy.Length - 5);
      }
      else if (sortBy.EndsWith(" ASC"))
      {
        sortBy = sortBy.Substring(0, sortBy.Length - 4);
      }
      #endregion

      this.Sort(sortBy, direction);
    }

    private void Sort(string sortBy, ListSortDirection direction)
    {
      if (this.ItemsSource == null)
        return;

      this.isSorting = true;

      try
      {
        #region Update GridView headers
        GridView gridView = this.View as GridView;
        if (gridView != null)
        {
          var column = gridView.Columns.FirstOrDefault(col => (col.Header as string) == sortBy);

          if (column != this.previousSortColumn && this.previousSortColumn != null)
            this.previousSortColumn.HeaderTemplate = null;

          if (direction == ListSortDirection.Ascending)
            column.HeaderTemplate = TryFindResource("HeaderTemplateArrowUp") as DataTemplate;
          else
            column.HeaderTemplate = TryFindResource("HeaderTemplateArrowDown") as DataTemplate;

          this.previousSortColumn = column;
          this.previousSortDirection = direction;
        }
        #endregion

        ICollectionView dataView = CollectionViewSource.GetDefaultView(this.ItemsSource);
        dataView.SortDescriptions.Clear();
        SortDescription sortDescription = new SortDescription(sortBy, direction);
        dataView.SortDescriptions.Add(sortDescription);
        dataView.Refresh();
      }
      finally
      {
        this.isSorting = false;
      }
      string dir = (direction == ListSortDirection.Ascending) ? " ASC" : " DESC";
      this.currentSortBy = sortBy + dir;
      this.SortBy = this.currentSortBy;
    }
    #endregion

    #region Sort: SortBy Dependency Proprerty

    /// <summary>
    /// The dependency property that allows us to bind the "SortBy" property
    /// </summary>
    public static readonly DependencyProperty SortByProperty
      = DependencyProperty.Register(
          "SortBy",
          typeof(string),
          typeof(SortableListView),
          new PropertyMetadata(SortByChangedCallback));

    /// <summary>
    /// Gets or sets how the list is sorted
    /// (use " ASC" or " DESC" suffixes to define the order of the sort)
    /// </summary>
    [Bindable(true, BindingDirection.TwoWay)]
    public string SortBy
    {
      get { return (string)GetValue(SortByProperty); }
      set { SetValue(SortByProperty, value); }
    }

    /// <summary>
    /// This method is called whenever the SortBy property
    /// is changed, and if it was changed from the outside,
    /// this method will sort the list
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="eventArgs"></param>
    public static void SortByChangedCallback(
      DependencyObject obj,
      DependencyPropertyChangedEventArgs eventArgs)
    {
      SortableListView listView = (SortableListView)obj;

      string sortBy = (string)eventArgs.NewValue;

      // if the currentSortBy field is the same as the new value
      // it means the "SortBy" property was changed from within
      // so we don't have to do anything.
      if (listView.currentSortBy == sortBy)
        return;

      listView.Sort(sortBy);
    }

    #endregion

    #region Event Handlers
    
    /// <summary>
    /// Responds to an <see cref="M:System.Windows.Controls.ItemsControl.OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs)"/>.
    /// </summary>
    /// <param name="e">The event arguments.</param>
    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
    {
      base.OnItemsChanged(e);

      if (!this.isSorting && !string.IsNullOrEmpty(this.SortBy))
        Sort(this.SortBy);
    }

    /// <summary>
    /// Called when the source of an item in a selector changes.
    /// </summary>
    /// <param name="oldValue">Old value of the source.</param>
    /// <param name="newValue">New value of the source.</param>
    protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
    {
      base.OnItemsSourceChanged(oldValue, newValue);

      if (!this.isSorting && !string.IsNullOrEmpty(this.SortBy))
        Sort(this.SortBy);
    }

    #endregion

    #region Drag&Drop

    private Point dragStartPoint;

    /// <summary>
    /// Invoked when an unhandled <see cref="E:System.Windows.UIElement.PreviewMouseLeftButtonDown"/> routed event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event.
    /// </summary>
    /// <param name="e">The <see cref="T:System.Windows.Input.MouseButtonEventArgs"/> that contains the event data. The event data reports that the left mouse button was pressed.</param>
    protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
    {
      base.OnPreviewMouseLeftButtonDown(e);

      this.dragStartPoint = e.GetPosition(null);
    }

    /// <summary>
    /// Called when a <see cref="T:System.Windows.Controls.ListBox"/> reports a mouse move.
    /// </summary>
    /// <param name="e">Provides data for <see cref="T:System.Windows.Input.MouseEventArgs"/>.</param>
    protected override void OnMouseMove(MouseEventArgs e)
    {
      base.OnMouseMove(e);

      if (e.LeftButton == MouseButtonState.Pressed)
      {
        Point mousePos = e.GetPosition(null);
        Vector diff = dragStartPoint - mousePos;

        if (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance &&
            Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
        {
          DataObject data = new DataObject(SelectedItems);
          DragDrop.DoDragDrop(this, data, DragDropEffects.Move);
        }
      }
    }

    #endregion
  }

  ///// <summary>
  ///// An event arguments class for the 
  ///// </summary>
  //public class ItemMouseButtonEventArgs : EventArgs
  //{
  //  public MouseButtonEventArgs MouseButtonEventArgs { get; private set; }
  //  public ListViewItem Item { get; private set; }

  //  public ItemMouseButtonEventArgs(ListViewItem item, MouseButtonEventArgs eventArgs)
  //  {
  //    this.Item = item;
  //    this.MouseButtonEventArgs = eventArgs;
  //  }
  //}
}
